package org.zoomdev.zoom.dao.impl;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.filter.pattern.PatternFilterFactory;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.common.utils.Page;
import org.zoomdev.zoom.dao.*;
import org.zoomdev.zoom.dao.adapters.RecordAdapter;
import org.zoomdev.zoom.dao.transaction.Trans;
import org.zoomdev.zoom.dao.transaction.Transactions;
import org.zoomdev.zoom.dao.utils.DaoUtils;
import org.zoomdev.zoom.common.utils.LambdaMethodExtractor;
import org.zoomdev.zoom.dao.utils.PageColumnInfo;
import org.zoomdev.zoom.dao.utils.PageColumnInfoBuilder;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 将来与ActiveRecord合并
 *
 * @param <T>
 */
public final class EntityActiveRecord<T> extends AbstractRecord implements EAr<T> {

    public static final boolean DEFAULT_IGNORE_ENTITY_CONDITION = false;
    private static final ConcurrentMap<String, Filter<EntityField>> patterFilterCache = new ConcurrentHashMap<>();
    public static boolean DEFAULT_IGNORE_NULL = true;
    public static boolean DEFAULT_STRICT = true;
    private final List<EntityField> entityFields;
    private Entity<T> entity;
    private boolean ignoreNull = DEFAULT_IGNORE_NULL;
    private boolean strict = DEFAULT_STRICT;

    //是否忽略实体类中的条件
    private boolean ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
    /**
     * 对字段进行筛选
     */
    private Filter<EntityField> filter;
    private RecordAdapter<T> adapter;

    private boolean rawMode = false;

    public EntityActiveRecord(Dao dao, Entity entity) {
        super(dao, new SimpleSqlBuilder(dao.getDriver()));
        this.entity = entity;
        this.entityFields = new ArrayList<>();
    }

    @SafeVarargs
    @Override
    public final EAr<T> select(SFunction<T, ?>... keys) {
        List<String> list = new ArrayList<>(keys.length);
        for (SFunction<T, ?> func : keys) {
            list.add(LambdaMethodExtractor.extraGetterName(func));
        }
        builder.select(list);
        return this;
    }

    @SafeVarargs
    @Override
    public final EAr<T> filter(SFunction<T, ?>... keys) {
        List<String> list = new ArrayList<>(keys.length);
        for (SFunction<T, ?> func : keys) {
            list.add(LambdaMethodExtractor.extraGetterName(func));
        }
        filter(StringUtils.join(list, "|"));
        return this;
    }

    @Override
    public EAr<T> where(Condition<T> condition) {
        builder.where(this, condition);
        return this;
    }

    @Override
    public EAr<T> orWhere(Condition<T> condition) {
        builder.orWhere(this, condition);
        return this;
    }

    @Override
    public EAr<T> filter(String filter) {
        this.filter = createPatternFilter(filter);
        return this;
    }

    @Override
    public EAr<T> ignoreNull(boolean value) {
        ignoreNull = value;
        return this;
    }

    @Override
    public EAr<T> ignoreEntityCondition() {
        ignoreEntityCondition = true;
        return this;
    }

    @Override
    public EAr<T> adapter(RecordAdapter<T> adapter) {
        this.adapter = adapter;
        return this;
    }

    @Override
    public List<T> find() {
        return execute(connection -> {
            entity.setQuerySource(builder);
            EntitySqlUtils.buildSelect(builder, entity, filter, entityFields);
            builder.buildSelect();
            return EntityActiveRecordUtils.executeQuery(
                    connection,
                    builder,
                    entityFields,
                    entity,
                    adapter,
                    dao.isOutput()
            );
        });
    }

    @Override
    public List<T> find(String sql, Object... args) {
        return execute(connection -> EntityActiveRecordUtils.executeQuery(connection,
                sql,
                Arrays.asList(args),
                entity,
                dao.isOutput()));
    }

    @Override
    public List<T> limit(final int position, final int size) {
        return execute(connection -> {
            entity.setQuerySource(builder);
            EntitySqlUtils.buildSelect(builder, entity, filter, entityFields);
            builder.buildLimit(position, size);
            return EntityActiveRecordUtils.executeQuery(
                    connection,
                    builder,
                    entityFields,
                    entity,
                    adapter,
                    dao.isOutput()
            );
        });
    }

    //SELECT * from t_qr_trans INNER JOIN(SELECT id from t_qr_trans LIMIT 100000,10) as b using(id);
    @Override
    public Page<T> position(final int position, final int size) {
        return execute(connection -> {
            entity.setQuerySource(builder);
            EntitySqlUtils.buildSelect(builder, entity, filter, entityFields);
            builder.buildLimit(position, size);
            List<T> list = EntityActiveRecordUtils.executeQuery(connection, builder, entityFields, entity, adapter, dao.isOutput());
            builder.clear(false);
            remove2(builder.values);
            //最后两个参数要移除掉
            int total = _value(connection, DaoUtils.SELECT_COUNT, int.class);
            int page = DaoUtils.position2page(position, size);
            return new Page<T>(list, page, size, total);
        });
    }

    @Override
    public Page<T> page(int page, int size) {
        if (page <= 0) page = 1;
        return position((page - 1) * size, size);
    }

    @Override
    public <P extends Page> P page(Class<P> pageClass, int page, int size) {
        if (page <= 0) page = 1;
        return position2(pageClass, (page - 1) * size, size);
    }

    public <P extends Page> P position2(Class<P> pageClass, final int position, final int size) {
        return execute(connection -> {
            entity.setQuerySource(builder);
            EntitySqlUtils.buildSelect(builder, entity, filter, entityFields);
            builder.buildLimit(position, size);
            List<T> list = EntityActiveRecordUtils.executeQuery(connection, builder, entityFields, entity, adapter, dao.isOutput());
            builder.clear(false);
            remove2(builder.values);
            //最后两个参数要移除掉

            PageColumnInfo[] infos = PageColumnInfoBuilder.get(pageClass);
            for (PageColumnInfo info : infos) {
                builder.selectRaw(info.getSelectRaw());
            }
            builder.buildSelect();
            Object[] values = EntityActiveRecordUtils.executeGetValues(connection, builder, infos.length, dao.isOutput());
            int page = DaoUtils.position2page(position, size);

            P ret = (P) Classes.newInstance(pageClass);
            ret.setList(list);
            ret.setPage(page);
            ret.setPageSize(size);

            for (int i = 0; i < infos.length; ++i) {
                PageColumnInfo info = infos[i];
                Object value = values[i];
                info.setValue(ret, value);
            }

            return ret;
        });
    }

    @Override
    public int update(final T data) {

        try {
            return execute(connection -> {
                assert (data != null);
                if (data instanceof org.zoomdev.zoom.dao.Record && strict) {
                    //检测一下
                    validateRecord((org.zoomdev.zoom.dao.Record) data);
                }
                if (!ignoreEntityCondition) {
                    EntitySqlUtils.entityCondition(builder, entity, data);
                }


                EntitySqlUtils.buildUpdate(
                        builder,
                        dao.getDriver(),
                        entity,
                        data,
                        filter,
                        ignoreNull);

                return EntityActiveRecordUtils.executeUpdate(
                        connection,
                        builder, dao.isOutput()

                );
            });
        } finally {
            ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
        }

    }

    @Override
    public T get() {
        return execute(connection -> getImpl());
    }

    public T get(final Object... values) {
        return execute(connection -> {
//            if (entity.getPrimaryKeys().length == 0) {
//                throw new DaoException("本表" + entity.getTable() + "没有主键，系统无法判断条件");
//            }

            if (entity.getPrimaryKeys().length > 0) {
                if (entity.getPrimaryKeys().length != values.length) {
                    throw new DaoException(
                            "参数个数" + values.length + "与主键个数" + entity.getPrimaryKeys().length + "不符");
                }
                int index = 0;
                for (EntityField adapter : entity.getPrimaryKeys()) {
                    builder.where(adapter.getColumnName(), values[index++]);
                }
            }

            return getImpl();
        });
    }

    @Override
    public int insert(final T data) {

        return execute(connection -> {
            assert (data != null);

            if (data instanceof org.zoomdev.zoom.dao.Record && strict) {
                validateRecord((org.zoomdev.zoom.dao.Record) data);
            }
            EntitySqlUtils.buildInsert(
                    builder,
                    dao.getDriver(),
                    entity,
                    data,
                    filter,
                    ignoreNull);
            return EntityActiveRecordUtils.executeInsert(
                    connection,
                    entity,
                    data,
                    builder, dao.isOutput()
            );
        });
    }

    @Override
    public int delete(final T data) {

        try {
            return execute(connection -> {

                if (!ignoreEntityCondition) {
                    EntitySqlUtils.entityCondition(builder, entity, data);
                }
                builder.table(entity.getTable());
                builder.buildDelete();

                return EntityActiveRecordUtils.executeUpdate(connection, builder, dao.isOutput());
            });
        } finally {
            //注意这里改回来
            ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
        }
    }

    @Override
    public Entity<T> getEntity() {
        return entity;
    }

    @Override
    public EAr<T> setEntity(Entity<T> entity) {
        this.entity = entity;
        return this;
    }

    @Override
    public EAr<T> strict(boolean strict) {
        this.strict = strict;
        return this;
    }

    @Override
    public int delete() {
        return execute(connection -> {
            builder.table(entity.getTable());
            builder.buildDelete();
            return ActiveRecordUtils.executeUpdate(
                    connection,
                    builder.sql(),
                    builder.values,
                    dao.isOutput()
            );
        });
    }

    @Override
    public int[] insert(final List<T> it) {
        return execute(connection -> BatchExecutor.executeInsert(connection, dao, entity, it, builder, dao.isOutput()));
    }

    @Override
    public int insertIgnoreDuplicated(final T data, final String... keys) {

        return execute(connection -> {
            assert (data != null);

            if (data instanceof org.zoomdev.zoom.dao.Record && strict) {
                validateRecord((org.zoomdev.zoom.dao.Record) data);
            }

            EntitySqlUtils.buildInsertIgnoreDuplicated(
                    builder,
                    dao.getDriver(),
                    entity,
                    data,
                    filter,
                    ignoreNull,
                    keys);


            return EntityActiveRecordUtils.executeInsert(
                    connection,
                    entity,
                    data,
                    builder, dao.isOutput()
            );
        });
    }

    @Override
    public int insertOrUpdate(final T data, final String... keys) {

        return execute(connection -> {
            assert (data != null);

            if (data instanceof org.zoomdev.zoom.dao.Record && strict) {
                validateRecord((org.zoomdev.zoom.dao.Record) data);
            }
            dao.getDriver().buildInsertOrUpdate(builder,
                    entity,
                    data,
                    filter,
                    ignoreNull,
                    keys);
            return EntityActiveRecordUtils.executeUpdate(
                    connection,
                    builder, dao.isOutput()
            );
        });
    }

    @Override
    public int update(final Iterable<T> it) {
        final MutableInt result = new MutableInt(0);
        Transactions.executeTrans(
                Trans.TRANSACTION_READ_UNCOMMITTED,
                () -> {
                    for (T t : it) {
                        result.add(update(t));
                    }
                });

        return result.getValue();
    }

    @Override
    public int delete(final Iterable<T> it) {
        final MutableInt result = new MutableInt(0);
        Transactions.executeTrans(
                Trans.TRANSACTION_READ_UNCOMMITTED,
                () -> {
                    for (T t : it) {
                        result.add(delete(t));
                    }
                });

        return result.getValue();
    }

    @Override
    public EAr<T> rawMode() {
        rawMode = true;
        return this;
    }

    private void validateRecord(org.zoomdev.zoom.dao.Record record) {
        Set<String> allKeys = new HashSet<>(record.size());
        allKeys.addAll(record.keySet());

        for (EntityField entityField : entity.getEntityFields()) {
            allKeys.remove(entityField.getFieldName());
        }

        if (!allKeys.isEmpty()) {
            throw new DaoException("Record中包含多余字段:" +
                    StringUtils.join(allKeys, ",") + "所有可能的字段为" + StringUtils.join(
                    entity.getAvailableFields(), ","
            ));
        }

    }

    public T getImpl() throws SQLException {
        entity.setQuerySource(builder);
        EntitySqlUtils.buildSelect(builder, entity, filter, entityFields);
        builder.buildSelect();
        return EntityActiveRecordUtils.executeGet(
                connection,
                builder,
                entity,
                entityFields,
                adapter,
                dao.isOutput()
        );
    }

    private static Filter<EntityField> createPatternFilter(final String filter) {
        return patterFilterCache.computeIfAbsent(filter,
                (String key) -> new EntitySqlUtils.PatterFilter(PatternFilterFactory.createFilter(key)));
    }

    @Override
    public <R> EAr<T> where(SFunction<T, R> func, Object value) {
        String key = LambdaMethodExtractor.extraGetterName(func);
        return where(key, value);
    }

    @Override
    public <R> EAr<T> where(boolean condition, SFunction<T, R> func, Object value) {
        String key = LambdaMethodExtractor.extraGetterName(func);
        return where(condition, key, value);
    }

    @Override
    public <R> EAr<T> orWhere(SFunction<T, R> key, Object value) {
        orWhere(LambdaMethodExtractor.extraGetterName(key), value);
        return this;
    }

    @Override
    public <R> EAr<T> orLike(SFunction<T, R> key, SqlBuilder.Like like, Object value) {
        orLike(LambdaMethodExtractor.extraGetterName(key), like, value);
        return this;
    }

    @Override
    public <R> EAr<T> like(SFunction<T, R> key, SqlBuilder.Like like, Object value) {
        like(LambdaMethodExtractor.extraGetterName(key), like, value);
        return this;
    }

    @Override
    public <R> EAr<T> like(boolean condition, SFunction<T, R> key, SqlBuilder.Like like, Object value) {
        if (condition) {
            like(LambdaMethodExtractor.extraGetterName(key), like, value);
        }
        return this;
    }

    @Override
    public <E> EAr<T> whereIn(SFunction<T, E> key, Collection<E> values) {
        whereIn(LambdaMethodExtractor.extraGetterName(key), values);
        return this;
    }

    @Override
    public EAr<T> where(String field, Object value) {
        builder.where(entity.getColumnName(field), value);
        return this;
    }

    @Override
    public EAr<T> where(boolean condition, String key, Object value) {
        if (condition) {
            where(key, value);
        }
        return this;
    }

    @Override
    public EAr<T> where(String field, Symbol symbol, Object value) {
        builder.where(entity.getColumnName(field), symbol, value);
        return this;
    }

    public <E> EAr<T> whereIn(String field, Collection<E> values) {
        builder.whereIn(entity.getColumnName(field), values);
        return this;
    }

    @Override
    public EAr<T> whereNotNull(String name) {
        builder.whereNotNull(entity.getColumnName(name));
        return this;
    }

    @Override
    public EAr<T> whereNull(String field) {
        builder.whereNull(entity.getColumnName(field));
        return this;
    }

    @Override
    public EAr<T> like(String name, SqlBuilder.Like like, Object value) {
        builder.like(entity.getColumnName(name), like, value);
        return this;
    }

    @Override
    public EAr<T> orWhere(String field, Object value) {
        builder.orWhere(entity.getColumnName(field), value);
        return this;
    }

    @Override
    public EAr<T> orLike(String name, SqlBuilder.Like like, Object value) {
        builder.orLike(entity.getColumnName(name), like, value);
        return this;
    }

    @Override
    public EAr<T> orderBy(String field, SqlBuilder.Sort sort) {
        builder.orderBy(entity.getColumnName(field), sort);
        return this;
    }

    @Override
    public EAr<T> groupBy(String field) {
        builder.groupBy(entity.getColumnName(field));
        return this;
    }

    @Override
    public EAr<T> having(String field, Symbol symbol, Object value) {
        if (EntitySqlUtils.TABLE_AND_COLUMN_PATTERN.matcher(field).matches()) {
            builder.having(entity.getColumnName(field), symbol, value);
        } else {
            builder.having(field, symbol, value);
        }

        return this;
    }

    @Override
    public EAr<T> union(SqlBuilder sqlBuilder) {
        throw new UnsupportedOperationException("未实现");
    }

    @Override
    public EAr<T> unionAll(SqlBuilder sqlBuilder) {
        throw new UnsupportedOperationException("未实现");
    }

    @Override
    public EAr<T> join(String table, String on) {
        return join(table, on, SqlBuilder.INNER);
    }

    @Override
    public EAr<T> join(String table, String on, String type) {
        builder.join(table, entity.parseOn(on), type);
        return this;
    }

    @Override
    public EAr<T> join(String table, String thisField, String otherField, SqlBuilder.JoinType type) {
        return this;
    }

    @Override
    public EAr<T> join(String table, String on, SqlBuilder.JoinType type) {
        return join(table, on, type.value());
    }

    @Override
    public EAr<T> select(String select) {
        return select(Arrays.asList(select.split(",")));
    }

    @Override
    public EAr<T> select(Iterable<String> select) {
        entity.select(entityFields, select);
        return this;
    }

    public <E> E value(final String key, final Class<E> typeOfE) {

        return execute(connection -> {
            entity.setQuerySource(builder);
            String field;
            if (EntitySqlUtils.TABLE_AND_COLUMN_PATTERN.matcher(key).matches()) {
                field = entity.getColumnName(key);
            } else {
                field = key;
            }
            return _value(connection, field, typeOfE);
        });
    }

    @Override
    protected void clear() {
        super.clear();
        entityFields.clear();
        filter = null;
        ignoreNull = DEFAULT_IGNORE_NULL;
        strict = DEFAULT_STRICT;
        ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
        adapter = null;
        rawMode = false;
    }
}
